/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.List;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.MethodNode;
import truerefactor.graph.StatementNode;
import truerefactor.graph.TypePair;

/**
 * PreserveWholeObject - An implementation of the Preserve Whole Object
 * refactoring technique
 * 
 * @author Isaac Griffith
 */
public class PreserveWholeObject extends Refactoring {

    /**
     * Creates a new instance of Preserve Whole Object
     */
    public PreserveWholeObject()
    {
        this.shortName = "PREOBJ";
        this.name = "Preserve Whole Object";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeGraph)
     */
    @Override
    public void execute(CodeGraph graph)
    {
        ClassNode paramClass = (ClassNode) operands.get(0);
        MethodNode method = (MethodNode) operands.get(1);

        createParameter(paramClass, method);
        removeOldParameters(paramClass, method);
        fixCallingMethods(paramClass, method);
    }

    /**
     * @param paramClass
     * @param method
     */
    private void fixCallingMethods(ClassNode paramClass, MethodNode method)
    {
        // 8. Remove the code in the calling method that obtains the deleted
        // parameters

        // FIXME: should be dealt with when code generation is considered
    }

    /**
     * Removes old parameters from the provided method node, which are now
     * stored in the provided class node, and generates a new parameter of the
     * same type as the provided class node
     * 
     * @param paramClass
     *            the new parameter class
     * @param method
     *            the method to remove parameters from
     */
    private void removeOldParameters(ClassNode paramClass, MethodNode method)
    {
        // 3. Determine which parameters should be obtained from the whole
        // object
        // 4. Take one parameter and replace references to it within the method
        // body by invoking an appropriate method on the whole object parameter
        // 5. Delete the parameter
        // 7. Repeat for each parameter that can be got from the whole object

        List<TypePair> typesToRemove = new ArrayList<TypePair>();
        List<String> paramsToRemove = new ArrayList<String>();

        for (String paramName : method.getParameters())
        {
            TypePair type = method.getParamInfo(paramName);
            List<AttributeNode> fields = paramClass.getAttributeByType(type.getType());

            if (!fields.isEmpty())
            {
                AttributeNode attr = fields.get(0);
                MethodNode getter = null;
                List<MethodNode> methods = attr.getAssociatedMethods();
                for (MethodNode temp : methods)
                {
                    if (temp.getIdentifier().toLowerCase().startsWith("get")
                            || temp.getIdentifier().toLowerCase().startsWith("is"))
                    {
                        getter = temp;
                        break;
                    }
                }

                if (getter != null)
                {
                    List<CodeNode> nodes = method.getStatements();
                    for (int i = 0; i < nodes.size() - 1; i++)
                    {
                        if (nodes.get(i) instanceof StatementNode)
                        {
                            StatementNode stmt = (StatementNode) nodes.get(i);
                            if (stmt.getCode().contains(paramName))
                            {
                                stmt.getCode().replace(
                                        paramName,
                                        method.getParameters().get(method.getParameters().size() - 1) + "."
                                                + getter.getIdentifier() + "()");
                            }
                        }
                    }

                    typesToRemove.add(type);
                    paramsToRemove.add(paramName);
                }
            }
        }

        method.getParameters().removeAll(paramsToRemove);
        method.getAllParamInfo().removeAll(typesToRemove);
    }

    /**
     * Creates a parameter in the provided method of the type of the provided
     * class
     * 
     * @param paramClass
     *            parameter class
     * @param method
     *            method to generate parameter for
     */
    private void createParameter(ClassNode paramClass, MethodNode method)
    {
        // 1. Create a new parameter for the whole object from which the data
        // comes
        TypePair type = new TypePair(paramClass.getClassName(), false);
        String name = paramClass.getClassName().toLowerCase();

        int count = 0;
        for (String n : method.getParameters())
        {
            if (n.contains(name))
                count++;
        }

        if (count > 0)
        {
            name += count;
        }

        method.addParameter(name, type);
    }
}
